﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Lesson4
{
    internal class Program
    {
        // 函数
        #region 函数
        // 函数也叫方法  是封装代码进行重复使用的一种机制
        // 函数主要作用
        // 1、封装代码
        // 2、提升代码复用率
        // 3、抽象行为

        // 函数写在哪里
        // 1、class 语句块中
        // 2、struct 结构体语句块中

        // 基本语法
        // static 返回类型 函数名(参数类型 参数1名, 参数类型 参数2名, ...)
        // {
        //      函数中的代码逻辑
        //      ......
        //      return 返回值; (如果有返回类型才返回)
        // }
        // 1. 关于static 不是必须的 
        // 2. - 返回类型，没有返回值时 用 void 一个关键字
        //    - 返回类型可以写任意类型 14种变量类型 + 复杂数据类型
        // 3. 关于函数名 使用帕斯卡命名法
        // 4. 参数 可以有 0 ~ n个  参数类型可以时是任意类型 14种变量类型 + 复杂数据类型
        //    多个参数之间用逗号隔开  参数名 驼峰命名法

        // 5. 当返回值类不是 void 时， 必须通过新的关键词 return 返回对应类型的内容
        //    如果是void 也可以选择使用return 但是不能返回某个类型值，直接 return;

        // 函数运用
        // 无参无返回值
        static void SayHello()
        {
            Console.WriteLine("Hello");
        }

        // 有参无返回值
        static void SayHello2(string message)
        {
            Console.WriteLine("给出的信息是{0}", message);
        }

        // 无参有返回值
        static string SayHello3()
        {
            return "world";
        }

        // 有参有返回值
        static string SayHello4(string message)
        {
            return message;
        }
        static int Sum(int a, int b)
        {
            return a + b;
        }

        // 有参与多返回值
        static int[] Calc(int a, int b)
        {
            int sum = a + b;
            int avg = sum / 2;
            //int[] arr = new int[] { sum, avg };
            //return arr;
            return new int[] { sum, avg };
        }

        // 关于 return
        // 即使没有返回值，也可以使用 return
        // return可以直接不执行之后的代码，直接返回到函数外部
        static void Compare(int a)
        {
            if(a < 1)
            {
                return;
            }
        }

        static string Speak(string message)
        {
            if(message == null)
            {
                return "";
            }
            return message;
        }

        #endregion

        #region ref和out
        // ref和out可以解决 在函数内部改变外部传入的内容 里面变了，外面也要变 涉及到值传递和引用传递

        // 使用：是函数参数的修饰符
        // 当传入的值类型参数在内部修改时，或者引用类型参数在内部重新声明时 外部的值会发生变化

        static void ChangeValueRef(ref int value)   // 声明时在参数前加上 ref
        {
            value = 99;
        }

        static void ChangeArrayRef(ref int[] arr)   // 声明时在参数前加上 ref
        {
            arr = new int[] { 1, 2, 3 };
        }

        static void ChangeValueOut(out int value)   // 声明时在参数前加上 out
        {
            value = 88;
        }

        static void ChangeArrayOut(out int[] arr)   // 声明时在参数前加上 out
        {
            arr = new int[] { 99, 555, 321 };
        }

        // ref和out的区别
        // ref传入的变量必须初始化  out不用
        // out传入的变量必须在内部赋值 ref不用

        // int a;  这样的a 传入带ref是不可以的 但是带out的是可以的 所以 a 传入ref后 可以不用赋值， 但a传入out中后必须要赋值

        #endregion

        #region 函数中变长参数和参数默认值
        // 变长参数的关键词  params

        static int SumArgs(params int[] arr)   // 这样就是表示参数是可变长的
        {
            int sum = 0;
            for(int i = 0; i < arr.Length; i++)
            {
                sum += arr[i];
            }
            return sum;
        }
        // params int[]  可以传入n个int参数 n可以为0，传入的参数会存在arr数组中
        // 注意： params 关键字后面的必须是数组
        //       数组的类型可以时任意类型
        //       函数的参数可以有 别的参数和params关键字修饰的参数
        //       函数的参数中最多只能出现一个params关键字， 并且移动式在最后 一组参数 前面的可以有n个其他参数
        static void Eat(string name, params string[] arr)
        {

        }

        // 参数默认值，有参数默认值的参数是可选参数
        // 支持多参数默认值，每个参数都可以有默认值
        // 如果要混用 可选参数必须在普通参数后面
        static void Fly(string name, string gender = "男")
        {

        }

        #endregion

        #region 函数重载
        // 重载的概念
        // 在同一语句块（class或struct）中
        // 函数（方法）名相同 参数的数量不同 或者 参数的数量相同 但参数的类型或顺序不同
        // 作用： 命名一组功能相似的函数，建设函数名的数量，避免命名空间的污染
        //       提升程序可读性

        // 使用：重载和返回值类型无关，只和参数类型、个数、顺序有关
        // 调用时 程序会自己根据传入的参数类型 判断使用哪个一个重载
        static int CalcSum(int a, int b)
        {
            return a + b;
        }

        // 数量不同
        static int CalcSum(int a, int b, int c)
        {
            return a + b + c;
        }

        // 数量相同 类型不同
        static float CalcSum(float a, float b)
        {
            return a + b;
        }

        // 数量相同 顺序不同
        static float CalcSum(int a, float b)
        {
            return a + b;
        }
        static float CalcSum(float b, int a)
        {
            return a + b;
        }

        // ref和out  ref和out只能算一个，写一个ref、一个out不能算重载，会报错
        static float CalcSum(ref float b, int a)
        {
            return a + b;
        }
        //static float CalcSum(out float b, int a)   // 和ref只能保留一个
        //{
        //    b = CalcSum(a, 0);
        //    return a + b;
        //}

        // 可变长参数
        static float CalcSum(double d, int a, params int[] m)
        {
            return 1;
        }
        #endregion

        #region 递归函数
        // 函数自己调用自己
        
        //static void Fun()
        //{
        //    Fun();
        //}

        // 一个正确的递归函数 1、要有结束调用的条件 2、结束调用的条件 必须改变 能够达到停止的目的

        static void Fun(int a)
        {
            if(a > 10)
            {
                return;
            }
            Console.WriteLine(a);
            ++a;
            Fun(a);  
        }

        #endregion

        static void Main(string[] args)
        {
            // 值类型和引用类型
            #region 值类型和引用类型
            // 引用类型： string 、数组 、 类
            // 值类型：所有 整形、浮点型、bool、char、结构体

            // 使用上的区别
            // 值类型
            int a = 10;
            // 引用类型
            int[] arr = new int[] { 1, 2, 3, 4 };

            int b = a;
            int[] arr2 = arr;

            Console.WriteLine("a={0}, b={1}", a, b);    // 10  10
            Console.WriteLine("arr[0]={0}, arr2[0]={1}", arr[0], arr2[0]); // 1  1

            b = 20;
            arr2[0] = 5;
            Console.WriteLine("修改后a={0}, b={1}", a, b);   // 10  20
            Console.WriteLine("修改后arr[0]={0}, arr2[0]={1}", arr[0], arr2[0]);  // 5   5

            // 值类型 在赋值时 是把内容拷贝过去  它变我不变
            // 引用类型赋值时 是让两者指向同一个引用 将引用赋值 它变我也变

            // 值类型存储在 栈空间  - 系统分配、自动回收、小而快
            // 引用类型 存储在 堆空间 - 手动申请和释放  大而慢

            arr2 = new int[] { 88, 99, 66 };
            Console.WriteLine("修改后arr[0]={0}, arr2[0]={1}", arr[0], arr2[0]);  // 5   88

            #endregion


            #region 特殊的引用类型 string

            // string 的 它变我不变
            string str1 = "123";
            string str2 = str1;

            str2 = "321";
            Console.WriteLine("str1={0}, str={1}", str1, str2);  // "123"  "321"

            // string 虽然方便，但有个小缺点 就是频繁改变 string 重新赋值，会产生 内存垃圾

            // 通过断点调试 在监视窗口中查看 内存信息
            // 断点到上面的声明str1的那一行，先输入 str1 和 str2 都显示 null
            // 然后输入 &str1 和 &str2 （&是取址符）会看见内存中的地址
            // 断点执行（按F10）会看到地址的改变，因为str1 和 str2开始赋值了

            #endregion

            // 函数调用
            SayHello();
            SayHello2("有参数");
            //SayHello3();
            Console.WriteLine(SayHello3());
            SayHello2(SayHello3());
            string h1 = SayHello4("hello world");
            Console.WriteLine(Sum(1, 2));

            int[] res = Calc(5, 7);
            Console.WriteLine("执行后的结果：和={0}，平均值={1}", res[0], res[1]);

            Compare(0);
            Console.WriteLine(Speak("sb"));

            // 调用带 ref 的函数时，参数前也要加上 ref
            int v = 1;
            ChangeValueRef(ref v);
            Console.WriteLine(v);   // 99

            ChangeValueOut(out v);
            Console.WriteLine(v);   // 88

            int[] array = new int[] { 1, 2, 3, 4, 5 };
            ChangeArrayRef(ref array);
            Console.WriteLine(array);  // 改变了

            ChangeArrayOut(out array);
            Console.WriteLine(array);  

            Console.WriteLine(SumArgs(1, 5, 999, 55, 22, 9, 8));

            //Fun(0); // 递归打印 0 - 10

            // 排序
            int[] arr_k = new int[] { 9, 5, 3, 6, 4, 2, 1, 7, 8 };
            
            // 冒泡排序
            for (int i = 0; i < arr_k.Length - 1; i++)
            {
                for (int j = 0; j < arr_k.Length - 1 - i; j++)   // -i 的目的是每次循环都会把最大的数放到最后，所以不用再比较
                {
                    if (arr_k[j] > arr_k[j + 1])
                    {
                        int temp = arr_k[j];
                        arr_k[j] = arr_k[j + 1];
                        arr_k[j + 1] = temp;
                    }
                }
            }

            // 冒泡排序优化： 特殊情况下，如果一次循环没有发生交换，说明已经是有序的了，可以直接退出
            bool isSort = false;
            for (int i = 0; i < arr_k.Length - 1; i++)
            {
                isSort = false;
                for (int j = 0; j < arr_k.Length - 1 - i; j++)
                {
                    if (arr_k[j] > arr_k[j + 1])
                    {
                        isSort = true;
                        int temp = arr_k[j];
                        arr_k[j] = arr_k[j + 1];
                        arr_k[j + 1] = temp;
                    }
                }

                if (!isSort)   // 如果没有发生交换，直接退出
                {
                    break;
                }
            }

            // 冒泡排序方式2 优化
            for (int i = arr_k.Length - 1; i > 0; i--)
            {
                for (int j = 0; j < i; j++)
                {
                    if (arr_k[j] < arr_k[j + 1])
                    {
                        int temp = arr_k[j];
                        arr_k[j] = arr_k[j + 1];
                        arr_k[j + 1] = temp;
                    }
                }
            }

            // 选择排序
            int maxIndex = 0;   // 默认第一个是最大值
            for (int i = 0; i < arr_k.Length; i++)  
            {
                for (int j = 1; j < arr_k.Length - i; j++)   // -i 的目的是 排除上一轮已经放好的位置
                {
                    if (arr_k[i] > arr_k[maxIndex])
                    {
                        maxIndex = i;       // 每次找出最大值，记录位置
                    }
                }

                if(maxIndex != arr_k.Length - 1 - i)   // 判断位置，如果相同就不要交换了   
                {
                    int temp = arr_k[maxIndex];
                    arr_k[maxIndex] = arr_k[arr_k.Length - 1 - i];
                    arr_k[arr_k.Length - 1 - i] = temp;
                }
            }

            // 选择排序方式2 
            for (int i = 0; i < arr_k.Length - 1; i++)
            {
                int minIndex = i;   // 默认第1个是最小值 从左边开始查，每次循环 i 都是最左边的
                for (int j = i + 1; j < arr_k.Length; j++)
                {
                    if (arr_k[j] < arr_k[minIndex])
                    {
                        minIndex = j;
                    }
                }
                if (minIndex != i)
                {
                    int temp = arr_k[i];
                    arr_k[i] = arr_k[minIndex];
                    arr_k[minIndex] = temp;
                }
            }

        }
    }
}
